/*
 * Copyright (c) 2004-2005 The Regents of The University of Michigan
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met: redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer;
 * redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution;
 * neither the name of the copyright holders nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Authors: Jin Yinghan
 *
 */

#include "base/misc.hh"
#include "base/intmath.hh"
#include "base/trace.hh"
#include "base/bitfield.hh"
#include "cpu/edge/pred/hyst_btype.hh"
#include "cpu/base.hh"

template<class Impl>
void
HysteresisTypeP<Impl>::init(unsigned _numEntries, unsigned _blockShiftAmt,
        std::string index_mode, std::string table_mode, bool exclude_seq)
{
    if (table_mode == "NormalTable") {
        tableMode = NormalTable;
    } else if (table_mode == "AliasFree") {
        tableMode = AliasFree;
    } else {
        fatal("Invalid table mode selected for hyst-based branch type"
                " predictor\n");
    }

    indexMode = InvalidIndexMode;

    numEntries = _numEntries;

    blockShiftAmt = _blockShiftAmt;

    if (tableMode == NormalTable) {

        hystTable.resize(numEntries, HystEntry(this));

        for (unsigned i = 0 ; i < numEntries ; ++i) {

            if (exclude_seq) {
                hystTable[i].branchType = TheISA::branch;
            } else {
                hystTable[i].branchType = TheISA::seq;
            }

            hystTable[i].hyst = 0;
        }
    }

    idxLength = floorLog2(numEntries);

    idxMask = (1 << idxLength) - 1;    

    if (index_mode == "Address") {
        indexMode = Address;
    } else if (index_mode == "AddressAndExit") {
        indexMode = AddressAndExit;
    } else if (index_mode == "AddressAndHist") {
        indexMode = AddressAndHist;
    } else if (index_mode == "AddressAndPath") {
        indexMode = AddressAndPath;
    } else if (index_mode == "AddressAndPredication") {
        indexMode = AddressAndPredication;
    } else {
        fatal("Invalid index mode selected for hyst-based branch type"
                " predictor\n");
    }

    DPRINTF(EdgePredUnit, "HystBType: numEntries:%i, idxLength:%i,"
            " idxMask:%#x, idxMode:%s, tableMode:%s\n",
            numEntries, idxLength, idxMask, index_mode, table_mode);
}

template<class Impl>
void
HysteresisTypeP<Impl>::reset()
{
    if (tableMode == NormalTable) {
        for (unsigned i = 0 ; i < numEntries ; ++i) {
            hystTable[i].branchType = TheISA::branch;
            hystTable[i].hyst = 0;
            hystTable[i].updated = false;
        }
    } else {
        hystTable.clear();
    }
}

template<class Impl>
unsigned
HysteresisTypeP<Impl>::idxGen(Addr PC, int addr_space_id, ExitID exit_id,
        uint64_t path_hist, uint64_t hist, uint64_t predication_hist)
{
    uint64_t index = ULL(-1);

    if (indexMode == AddressAndExit) {

        assert(addr_space_id >= 0 && addr_space_id <= 15);

        int reversed_as_id = ((addr_space_id & 0x1) << 3)
                            | ((addr_space_id & 0x2) << 1)
                            | ((addr_space_id & 0x4) >> 1)
                            | ((addr_space_id & 0x8) >> 3);

        assert (exit_id >= 0 && exit_id <= 7);

        int reversed_exit = ((exit_id & 0x1) << 2)
                           | (exit_id & 0x2)
                           | ((exit_id & 0x4) >> 2);

        index = ((PC >> blockShiftAmt)
                ^ ((reversed_exit << (idxLength - 3)) |
                (reversed_as_id << (idxLength- 7))));

    } else if (indexMode == Address) {

        index = (PC >> blockShiftAmt);

    } else if (indexMode == AddressAndHist) {

        index = ((PC >> blockShiftAmt) ^ (hist & 0xff));

    } else if (indexMode == AddressAndPath) {

        uint64_t pattern = bits(path_hist, 7, 0);

        index = (((PC >> blockShiftAmt) << 8) | (pattern & 0xff));

    } else if (indexMode == AddressAndPredication) {

        index = (PC >> blockShiftAmt) ^ predication_hist;

    } else {
        panic("Invalid index mode selected for hyst-based branch type"
                " predictor\n");
    }

    if (tableMode != AliasFree) {
        index &= idxMask;
    }

    return index;    
}

template<class Impl>
TheISA::ExitType
HysteresisTypeP<Impl>::lookup(Addr block_pc, int addr_space_id, ExitID exit_id,
        uint64_t path_hist, uint64_t hist, uint64_t predication_hist)
{
    unsigned index = idxGen(block_pc, addr_space_id, exit_id, path_hist,
            hist, predication_hist);

    //HystEntry hyst_table_entry = hystTable[index];

    if (hystTable[index].updated) {
        DPRINTF(EdgePredUnit,"Block [PC:%#x,idx:%i]:BTP updated before.\n", 
                block_pc, index);
    } else {
        DPRINTF(EdgePredUnit,"Block [PC:%#x,idx:%i]:BTP first accessed.\n", 
                block_pc, index);
    }

    return hystTable[index].branchType;
}

template<class Impl>
void
HysteresisTypeP<Impl>::update(Addr block_pc, int addr_space_id, ExitID exit_id,
        uint64_t path_hist, uint64_t hist, uint64_t predication_hist,
        ExitType branchType)
{
    unsigned index = idxGen(block_pc, addr_space_id, exit_id, path_hist, 
            hist, predication_hist);

    TheISA::ExitType old_type = hystTable[index].branchType;

    //HystEntry hyst_table_entry = hystTable[index];
//    HystEntry new_hyst_table_entry(this, HystEntry);

//    new_hyst_table_entry.branchType =
//        hyst_table_entry.hyst ? hyst_table_entry.branchType : branchType;

    hystTable[index].branchType =
        hystTable[index].hyst ? hystTable[index].branchType : branchType;

    hystTable[index].hyst = (old_type == branchType);

    hystTable[index].updated = true;

//    hystTable[index] = new_hyst_table_entry;

    if (hystTable[index].branchType == TheISA::branch) {
        DPRINTF(EdgePredUnit, "Block [PC:%#x,Exit:%i,idx:%i]:BTP"
                " updated(branch).\n",
                block_pc, exit_id,index);
    } else if (hystTable[index].branchType == TheISA::call) {
        DPRINTF(EdgePredUnit, "Block [PC:%#x,Exit:%i,idx:%i]:BTP"
                " updated(call).\n",
                block_pc, exit_id,index);        
    } else if (hystTable[index].branchType == TheISA::ret) {
        DPRINTF(EdgePredUnit, "Block [PC:%#x,Exit:%i,idx:%i]:BTP"
                " updated(return).\n", 
                block_pc, exit_id,index);        
    } else if (hystTable[index].branchType == TheISA::seq) {
        DPRINTF(EdgePredUnit, "Block [PC:%#x,Exit:%i,idx:%i]:BTP"
                " updated(seq).\n",
                block_pc, exit_id,index);        
    } else {
        panic("Unrecogonized branch type when update the BTP\n");
    }
}

